package ee.az.test.lighting;

import ee.az.geometry.AngleArc;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author Artur Zinatullin (artur.zinatullin@gmail.com) 2010
 */
public class HorizonVisibilityMap extends VisibilityMapImpl {
	
	public HorizonVisibilityMap(Location viewPoint, TransparencyMap transparencyMap) {
		super(viewPoint.getX(), viewPoint.getY(), transparencyMap);
	}

	@Override
	protected void initVisibilityMap() {
		Horizon horizon = new Horizon();
		setValue(getViewPointX(), getViewPointY(), new VisibleSpot());
		int maxRadius = getMaxRadius();
		for (int r=1; r<maxRadius; r++) {
			processRing(horizon, r);
			if (horizon.isFullCircle()) { // TODO profile without this
				return;
			}
		}
	}
	
	private void processRing(Horizon horizon, int r) {
		int x = getViewPointX();
		int y = getViewPointY();
		// TODO use horizon to optimize
		for (int i=-r; i<r; i++) {
			processSpot(x+i, y-r, horizon, x, y);
		}
		for (int i=-r; i<r; i++) {
			processSpot(x+r, y+i, horizon, x, y);
		}
		for (int i=-r; i<r; i++) {
			processSpot(x-i, y+r, horizon, x, y);
		}
		for (int i=-r; i<r; i++) {
			processSpot(x-r, y-i, horizon, x, y);
		}
	}

	private void processSpot(int x, int y, Horizon horizon, int viewPointX, int viewPointY) {
		if (x < 0 || y < 0 || x >= getWidth() || y >= getHeight()) {
			return;
		}
		ee.az.geometry.AngleArc spotArc = AngleArc.getArc(x, y, viewPointX, viewPointY);
		if (!horizon.contains(spotArc)) {
			setValue(x, y, new VisibleSpot());
		}
		if (!transparencyMap.isTransparent(x, y)) {
			horizon.add(spotArc);
		}
	}

	private int getMaxRadius() {
		int x = getViewPointX();
		int y = getViewPointY();
		x = Math.max(x, getWidth()-x);
		y = Math.max(x, getHeight()-y);
		return Math.max(x, y);
	}
	
	@Override
	protected VisibilitySpot createDefaultSpot() {
		return new InvisibleSpot();
	}
	
	public static abstract class Location {
		abstract int getX();
		abstract int getY();
	}
	
	public static class Horizon {
		private Collection<AngleArc> arcs = new ArrayList<AngleArc>();
		public boolean isFullCircle() {
			return !arcs.isEmpty() && arcs.iterator().next().isFullCircle();
		}
		public boolean contains(AngleArc arc) {
			for (AngleArc a : arcs) {
				if (a.contains(arc)) {
					return true;
				}
			}
			return false;
		}
		public void add(AngleArc arc) {
			if (isFullCircle()) {
				return;
			}
			for (Iterator<AngleArc> it = arcs.iterator(); it.hasNext();) {
				AngleArc a = it.next();
				if (a.intersects(arc)) {
					arc = a.sum(arc);
					it.remove();
				}
			}
			arcs.add(arc);
		}
		public Collection<AngleArc> getArcs() {
			return arcs;
		}
	}
}
